package view.mt4j.workspace;

import java.beans.PropertyChangeEvent;

import model.Statechart;
import model.Workspace;
import model.Workspace.Property;
import model.event.StatechartDimensionChangeEvent;
import model.event.StatechartLocationChangeEvent;
import model.util.Dimension;
import model.util.Point;

import org.mt4j.MTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.visibleComponents.font.FontManager;
import org.mt4j.components.visibleComponents.shapes.AbstractShape;
import org.mt4j.components.visibleComponents.shapes.MTEllipse;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.widgets.MTTextArea;
import org.mt4j.input.gestureAction.DefaultRotateAction;
import org.mt4j.input.gestureAction.TapAndHoldVisualizer;
import org.mt4j.input.inputProcessors.IGestureEventListener;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleEvent;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor.TapAndHoldEvent;
import org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor.TapAndHoldProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapEvent;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
import org.mt4j.util.MTColor;
import org.mt4j.util.math.Vector3D;
import org.mt4jx.util.MTColors;

import processing.core.PImage;

import controller.ApplicationController;
import controller.view.StatechartController;
import controller.view.WorkspaceController;

import util.CollectionChangeEvent;
import util.CollectionChangeListener;
import view.interfaces.workspace.WorkspaceView;
import view.mt4j.component.StatechartViewImpl;
import view.mt4j.component.ghost.MTRectangleGhost;
import view.mt4j.util.KeyboardHandler;
import view.mt4j.util.ProcessorHelper;
import view.mt4j.util.TextInputListener;

public class WorkspaceViewImpl extends MTRectangle implements WorkspaceView,
		CollectionChangeListener<Statechart> {

	private static final float WORKSPACE_INPUT_AREA_WIDTH = 200f;
	private static final float WORKSPACE_INPUT_AREA_HEIGHT = 50f;
	private final Workspace workspace;
	private ApplicationController applicationController;
	private WorkspaceController workspaceController;
	private MTRectangle resizeRectangle;
	private MTEllipse northButton, southButton;// , westButton, eastButton;
	private MTApplication mtApplication;
	private MTTextArea workspaceInputTextArea;
	// private MTKeyboard workspaceKeyboard;
	private MTEllipse workspaceInputValidateButton;
	private MTRectangle workspaceInputContainer;
	private KeyboardHandler keyboardHandler;
	private MTTextArea userNameTextArea;

	public WorkspaceViewImpl(MTApplication mtApplication,
			final Workspace workspace) {
		super(mtApplication, 0, 0, 0, 0);
		setAnchor(PositionAnchor.UPPER_LEFT);
		this.mtApplication = mtApplication;
		this.workspace = workspace;
		setSizeLocal(workspace.getSize().width, workspace.getSize().height);
		setFillColor(MTColors.LIGHTGRAY);
		setStrokeColor(MTColor.BLACK);
		setStrokeWeight(2f);
		this.workspace.addPropertyChangeListener(Property.STATECHART_LOCATION,
				this);
		this.workspace.addPropertyChangeListener(Property.STATECHART_DIMENSION,
				this);
		this.workspace.addPropertyChangeListener(Property.DIMENSION, this);
		this.workspace.addPropertyChangeListener(Property.LOCATION, this);
		this.workspace.addCollectionChangeListener(this);
		addResizeButtons();
		this.removeAllGestureEventListeners();
		this.addGestureListener(DragProcessor.class,
				new IGestureEventListener() {

					@Override
					public boolean processGestureEvent(MTGestureEvent ge) {
						if (ge instanceof DragEvent) {
							DragEvent dragEvent = (DragEvent) ge;
							workspace.setLocation(new Point(workspace
									.getLocation().x
									+ dragEvent.getTranslationVect().x,
									workspace.getLocation().y
											+ dragEvent.getTranslationVect().y));

						}
						return false;
					}
				});
		this.addGestureListener(RotateProcessor.class,
				new DefaultRotateAction());
		this.addGestureListener(ScaleProcessor.class,
				new IGestureEventListener() {

					@Override
					public boolean processGestureEvent(MTGestureEvent ge) {
						ScaleEvent se = (ScaleEvent) ge;
						WorkspaceViewImpl.this.scale(se.getScaleFactorX(),
								se.getScaleFactorY(), se.getScaleFactorX(),
								se.getScalingPoint());
						return false;
					}

				});
		addMenu();
		addUserName();
	}

	private void addUserName() {
		 userNameTextArea = new MTTextArea(getRenderer(), FontManager
				.getInstance().createFont(getRenderer(), "arial.ttf", 18,
						MTColor.WHITE));
		addChild(userNameTextArea);
		userNameTextArea.setText(workspace.getUser().getName());
		userNameTextArea.setPositionRelativeToParent(new Vector3D(this
				.getWidthXY(TransformSpace.LOCAL) / 2f, -20f));
		userNameTextArea.removeAllGestureEventListeners();
		userNameTextArea.setNoFill(true);
		userNameTextArea.setNoStroke(true);
	}

	public WorkspaceController getWorkspaceController() {
		return workspaceController;
	}

	private void addMenu() {
		System.out.println("display menu");
		registerInputProcessor(new TapAndHoldProcessor(mtApplication, 500));
		addGestureListener(TapAndHoldProcessor.class, new TapAndHoldVisualizer(
				mtApplication, this));
		addGestureListener(TapAndHoldProcessor.class,
				new IGestureEventListener() {
					@Override
					public boolean processGestureEvent(final MTGestureEvent ge) {
						final TapAndHoldEvent th = (TapAndHoldEvent) ge;
						switch (th.getId()) {
						case TapAndHoldEvent.GESTURE_ENDED:

							if (th.getElapsedTimeNormalized() < 1) {
								break;
							}
							WorkspaceMenuImpl workspaceMenuImpl = new WorkspaceMenuImpl(
									getRenderer(),
									WorkspaceViewImpl.this,
									workspaceController,
									th.getLocationOnScreen().x
											- getPosition(TransformSpace.GLOBAL).x,
									th.getLocationOnScreen().y
											- getPosition(TransformSpace.GLOBAL).y);
							workspaceMenuImpl.open(th.getLocationOnScreen().x,
									th.getLocationOnScreen().y);
							break;
						default:
							break;
						}
						return false;
					}
				});
	}

	private void addResizeButtons() {
		addResizeInvisibleRectangle();
		addButtons();
	}

	private void addResizeInvisibleRectangle() {
		resizeRectangle = new MTRectangle(getRenderer(), 0, 0);
		resizeRectangle.setComposite(true);
		resizeRectangle.setNoFill(true);
		resizeRectangle.setNoStroke(true);
		this.addChild(resizeRectangle);
		resizeRectangle.removeAllGestureEventListeners();
		resizeRectangle.addGestureListener(ScaleProcessor.class,
				new IGestureEventListener() {

					private Vector3D firstCursorStart;
					private Vector3D secondCursorStart;
					private MTRectangle ghost;

					private float sx;
					private float sy;

					@Override
					public boolean processGestureEvent(MTGestureEvent ge) {
						ScaleEvent scaleEvent = (ScaleEvent) ge;
						switch (ge.getId()) {

						case MTGestureEvent.GESTURE_STARTED: {
							System.out.println("SCALING ...");
							sx = 1f;
							sy = 1f;
							firstCursorStart = scaleEvent.getFirstCursor()
									.getPosition();
							secondCursorStart = scaleEvent.getSecondCursor()
									.getPosition();
							break;
						}
						case MTGestureEvent.GESTURE_UPDATED: {
							Vector3D firstCursorEnd = scaleEvent
									.getFirstCursor().getPosition();
							Vector3D secondCursorEnd = scaleEvent
									.getSecondCursor().getPosition();

							sx = (secondCursorEnd.x - firstCursorEnd.x)
									/ (secondCursorStart.x - firstCursorStart.x);
							sy = (secondCursorEnd.y - firstCursorEnd.y)
									/ (secondCursorStart.y - firstCursorStart.y);
							if (ghost == null) {
								Point ghostPosition = new Point(workspace
										.getLocation().x

								- workspace.getSize().width / 2, workspace
										.getLocation().y
										- workspace.getSize().height / 2);
								ghost = new MTRectangleGhost(getRenderer(),
										ghostPosition, workspace.getSize(),
										MTColor.BLUE);
								addChild(ghost);
							}

							if (sx > 0 && sy > 0) {
								ghost.setSizeLocal(workspace.getSize().width
										* sx, workspace.getSize().height * sy);
								ghost.setPositionGlobal(new Vector3D(
										(scaleEvent.getFirstCursor()
												.getPosition().x + scaleEvent
												.getSecondCursor()
												.getPosition().x) / 2,
										(scaleEvent.getFirstCursor()
												.getPosition().y + scaleEvent
												.getSecondCursor()
												.getPosition().y) / 2));
							}

							break;
						}

						case MTGestureEvent.GESTURE_ENDED: {
							workspace.setSize(new Dimension(
									workspace.getSize().width * sx, workspace
											.getSize().height * sy));
							workspace
									.setLocation(new Point(
											(scaleEvent.getFirstCursor()
													.getPosition().x + scaleEvent
													.getSecondCursor()
													.getPosition().x)
													/ 2
													- workspace.getSize().width
													/ 2f,
											(scaleEvent.getFirstCursor()
													.getPosition().y + scaleEvent
													.getSecondCursor()
													.getPosition().y)
													/ 2
													- workspace.getSize().height
													/ 2f));
							
							System.out.println("SCALING ENDED");
							if (ghost != null) {
								ghost.removeFromParent();
								ghost = null;
							}
							break;
						}
						default:
							break;
						}
						return false;
					}
				});
	}

	private void addButtons() {
		northButton = addButton(this.getWidthXY(TransformSpace.LOCAL) / -2f,
				this.getHeightXY(TransformSpace.LOCAL) / -2f);
		southButton = addButton(this.getWidthXY(TransformSpace.LOCAL) / 2f,
				this.getHeightXY(TransformSpace.LOCAL) / 2f);
		// westButton = addButton(this.getWidthXY(TransformSpace.LOCAL) / -2f,
		// 0f);
		// eastButton = addButton(this.getWidthXY(TransformSpace.LOCAL) / 2f,
		// 0f);
	}

	private MTEllipse addButton(Float x, Float y) {
		MTEllipse button = new MTEllipse(getRenderer(), new Vector3D(), 10f,
				10f);
		button.setFillColor(MTColor.WHITE);
		button.setStrokeColor(MTColor.BLACK);
		button.setStrokeWeight(1f);
		resizeRectangle.addChild(button);
		button.setPositionRelativeToParent(this.getCenterPointLocal().getAdded(
				new Vector3D(x, y)));
		return button;
	}

	@Override
	public void setController(ApplicationController applicationController,
			WorkspaceController workspaceController) {
		this.applicationController = applicationController;
		this.workspaceController = workspaceController;
	}

	public ApplicationController getApplicationController() {
		return applicationController;
	}

	public Workspace getWorkspace() {
		return workspace;
	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(Property.DIMENSION.name())) {
			Dimension dimension = (Dimension) evt.getNewValue();
			setSizeLocal(dimension.width, dimension.height);
			moveButtons();
			userNameTextArea.setPositionRelativeToParent(new Vector3D(this
					.getWidthXY(TransformSpace.LOCAL) / 2f, -20f));

		} else if (evt.getPropertyName().equals(Property.LOCATION.name())) {
			Point location = (Point) evt.getNewValue();
			setPositionRelativeToParent(new Vector3D(location.x, location.y));

		} else if (evt.getPropertyName().equals(
				Property.STATECHART_LOCATION.name())) {
			System.out.println("Property.STATECHART_LOCATION");
			Point location = (Point) evt.getNewValue();
			Statechart statechart = ((StatechartLocationChangeEvent) evt)
					.getStatechart();
			StatechartViewImpl statechartView = findStatechartInChildViews(statechart);
			if (statechartView != null) {
				System.out
						.println("Property.STATECHART_LOCATION : setting position");
				statechartView.setLocation(new Point(location.x, location.y));
			} else {
				System.out
						.println("Property.STATECHART_LOCATION : view not found");

			}
		} else if (evt.getPropertyName().equals(
				Property.STATECHART_DIMENSION.name())) {
			System.out.println("Property.STATECHART_DIMENSION");
			Dimension size = (Dimension) evt.getNewValue();
			Statechart statechart = ((StatechartDimensionChangeEvent) evt)
					.getStatechart();
			StatechartViewImpl statechartView = findStatechartInChildViews(statechart);
			if (statechartView != null) {
				System.out
						.println("Property.STATECHART_DIMENSION : setting size");
				statechartView.setSize(size);
			}
		}
	}

	private StatechartViewImpl findStatechartInChildViews(Statechart statechart) {
		for (MTComponent child : this.getChildren()) {
			System.out.println(child);
			if (child instanceof StatechartViewImpl
					&& ((StatechartViewImpl) child).getStatechart().equals(
							statechart)) {
				return (StatechartViewImpl) child;
			}
		}
		return null;
	}

	private void moveButtons() {
		northButton.setPositionRelativeToParent(this.getCenterPointLocal()
				.getAdded(
						new Vector3D(this.getWidthXY(TransformSpace.LOCAL)
								/ -2f, this.getHeightXY(TransformSpace.LOCAL)
								/ -2f)));
		southButton.setPositionRelativeToParent(this.getCenterPointLocal()
				.getAdded(
						new Vector3D(
								this.getWidthXY(TransformSpace.LOCAL) / 2f,
								this.getHeightXY(TransformSpace.LOCAL) / 2f)));
		// westButton.setPositionRelativeToParent(this.getCenterPointLocal()
		// .getAdded(
		// new Vector3D(this.getWidthXY(TransformSpace.LOCAL)
		// / -2f, 0f)));
		// eastButton
		// .setPositionRelativeToParent(this.getCenterPointLocal()
		// .getAdded(
		// new Vector3D(this
		// .getWidthXY(TransformSpace.LOCAL) / 2f,
		// 0f)));
	}

	public void setLocation(float x, float y) {
		workspace.setLocation(new Point(x, y));
	}

	@Override
	public void displayWorkspaceInput(
			final TextInputListener workspaceKeyboardListener) {
		if (workspaceInputContainer != null) {
			if (this.containsChild(workspaceInputContainer)) {
				this.removeChild(workspaceInputContainer);
			}
			workspaceInputContainer.destroy();
		}
		workspaceInputContainer = new MTRectangle(getRenderer(), 0, 0, 0, 0);
		workspaceInputContainer.setNoFill(true);
		workspaceInputContainer.setStrokeColor(MTColor.BLACK);
		workspaceInputContainer.setSizeLocal(WORKSPACE_INPUT_AREA_WIDTH,
				WORKSPACE_INPUT_AREA_HEIGHT);
		this.addChild(workspaceInputContainer);
		workspaceInputContainer.setPositionRelativeToParent(new Vector3D(
				workspace.getSize().width / 2f - WORKSPACE_INPUT_AREA_WIDTH
						/ 2f, workspace.getSize().height
						+ WORKSPACE_INPUT_AREA_HEIGHT / 2f));

		addWorkspaceInputText(workspaceKeyboardListener);
		addWorkspaceInputValidateButton(workspaceKeyboardListener);
	}

	private void addWorkspaceInputValidateButton(
			final TextInputListener workspaceKeyboardListener) {
		PImage checkImage = getRenderer().loadImage("resources/check.png");
		workspaceInputValidateButton = new MTEllipse(getRenderer(),
				new Vector3D(), (WORKSPACE_INPUT_AREA_HEIGHT - 10f) / 2f,
				(WORKSPACE_INPUT_AREA_HEIGHT - 10f) / 2f);
		workspaceInputValidateButton.setFillColor(MTColor.GRAY);
		workspaceInputValidateButton.setTexture(checkImage);
		workspaceInputContainer.addChild(workspaceInputValidateButton);
		workspaceInputValidateButton
				.setPositionRelativeToParent(workspaceInputContainer
						.getCenterPointLocal().getAdded(new Vector3D(72f, 0f)));

		workspaceInputValidateButton.removeAllGestureEventListeners();
		ProcessorHelper.delegateDragScaleRotateEventTo(this,
				workspaceInputValidateButton);
		workspaceInputValidateButton.registerInputProcessor(new TapProcessor(
				getRenderer()));

		workspaceInputValidateButton.addGestureListener(TapProcessor.class,
				new IGestureEventListener() {

					@Override
					public boolean processGestureEvent(MTGestureEvent ge) {
						TapEvent te = (TapEvent) ge;
						switch (te.getId()) {
						case TapEvent.GESTURE_STARTED:
							if (workspaceKeyboardListener
									.validateInput(workspaceInputTextArea
											.getText())) {
								workspaceInputValidateButton
										.setStrokeColor(MTColor.BLACK);
								workspaceInputValidateButton.setNoStroke(false);
							}
							break;
						case TapEvent.GESTURE_ENDED:
							workspaceInputValidateButton.setNoStroke(true);
							if (te.isTapped()) {
								if (workspaceKeyboardListener
										.validateInput(workspaceInputTextArea
												.getText())) {
									workspaceKeyboardListener
											.onValidationSuccessfull(workspaceInputTextArea
													.getText());
									if (getKeyboardHandler().getKeyboard() != null) {
										getKeyboardHandler().getKeyboard()
												.close();
									}
								} else {
									workspaceKeyboardListener
											.onValidationFailed(workspaceInputTextArea
													.getText());
								}
							}
							break;
						default:
							break;
						}
						return false;
					}
				});
	}

	protected KeyboardHandler getKeyboardHandler() {
		return keyboardHandler;
	}

	private void addWorkspaceInputText(
			final TextInputListener workspaceKeyboardListener) {
		workspaceInputTextArea = new MTTextArea(getRenderer(), 0, 0,
				workspaceInputContainer.getWidthXY(TransformSpace.LOCAL) - 60,
				workspaceInputContainer.getHeightXY(TransformSpace.LOCAL) - 10,
				FontManager.getInstance().createFont(getRenderer(),
						"arial.ttf", 20, MTColor.BLACK));
		workspaceInputTextArea.setFillColor(MTColor.WHITE);
		workspaceInputTextArea.setText("aa");
		workspaceInputTextArea.setStrokeColor(MTColor.BLACK);
		workspaceInputTextArea.setStrokeWeight(2f);
		workspaceInputTextArea.setInnerPaddingTop(8);

		workspaceInputContainer.removeAllGestureEventListeners();
		workspaceInputTextArea.removeAllGestureEventListeners();
		ProcessorHelper.delegateDragScaleRotateEventTo(this,
				workspaceInputContainer);
		ProcessorHelper.delegateDragScaleRotateEventTo(workspaceInputContainer,
				workspaceInputTextArea);

		updateUsernamePosition();
		keyboardHandler = new KeyboardHandler(getRenderer(), null,
				workspaceInputTextArea, this, new Vector3D(
						workspace.getSize().width / 2f
								- WORKSPACE_INPUT_AREA_WIDTH / 2f,
						workspace.getSize().height
								+ WORKSPACE_INPUT_AREA_HEIGHT + 245f / 2f)) {

			@Override
			public void onTextChange() {
				updateTextFollowingTextArea();
				updateInputValidateButton(workspaceInputTextArea.getText());
			}

			private void updateInputValidateButton(String text) {
				if (text != null && text.length() >= 2
						&& workspaceKeyboardListener.validateInput(text)) {
					workspaceInputValidateButton.setFillColor(MTColor.WHITE);
				} else {
					workspaceInputValidateButton.setFillColor(MTColor.GRAY);
				}
			}

			@Override
			public void onClose() {
				workspaceInputContainer.removeFromParent();
			}

		};
		workspaceInputTextArea.addGestureListener(TapProcessor.class,
				keyboardHandler);

		workspaceInputContainer.addChild(workspaceInputTextArea);
		workspaceInputTextArea
				.setPositionRelativeToParent(workspaceInputContainer
						.getCenterPointLocal().getAdded(new Vector3D(-25f, 0f)));
	}

	private void updateUsernamePosition() {
		workspaceInputTextArea.registerInputProcessor(new TapProcessor(
				getRenderer()));
	}

	@Override
	public void collectionChanged(CollectionChangeEvent<Statechart> ev) {
		if (ev.getType().name().equals(CollectionChangeEvent.Type.ADD.name())) {
			Statechart statechart = ev.getItem();
			StatechartViewImpl statechartView = new StatechartViewImpl(
					getRenderer(), this, statechart);
			StatechartController statechartController = workspaceController
					.createStatechartController(statechart, statechartView);
			statechartView.setController(applicationController,
					workspaceController, statechartController);
			this.addChild(statechartView);

		} else if (ev.getType().name()
				.equals(CollectionChangeEvent.Type.REMOVE.name())) {
			Statechart workspace = ev.getItem();
			for (MTComponent child : this.getChildren()) {
				if (child instanceof StatechartViewImpl
						&& ((StatechartViewImpl) child).getStatechart().equals(
								workspace)) {
					this.removeChild(child);
				}
			}
		}
	}

}
